(decryptedData);
if (!qrCodeAuthMessagePayloadValidator.is(payload)) {
return null;
}
return payload;
}
function QRCodeLogin(): React.Node {
const [qrData, setQRData] =
React.useState{ +deviceID: string, +aesKey: string }>();
const { setUnauthorizedDeviceID } = useTunnelbroker();
const generateQRCode = React.useCallback(async () => {
try {
const [ed25519, rawAESKey] = await Promise.all([
getContentSigningKey(),
generateKeyCommon(crypto),
]);
const aesKeyAsHexString: string = uintArrayToHexString(rawAESKey);
setUnauthorizedDeviceID(ed25519);
setQRData({ deviceID: ed25519, aesKey: aesKeyAsHexString });
} catch (err) {
console.error('Failed to generate QR Code:', err);
}
}, [setUnauthorizedDeviceID]);
const { pushModal } = useModalContext();
const logInSecondaryDevice = useSecondaryDeviceLogIn();
const performRegistration = React.useCallback(
async (userID: string) => {
try {
await logInSecondaryDevice(userID);
} catch (err) {
console.error('Secondary device registration error:', err);
const messageForException = getMessageForException(err);
if (
messageForException === 'client_version_unsupported' ||
- messageForException === 'Unsupported version'
+ messageForException === 'unsupported_version'
) {
pushModal();
} else {
pushModal(Uhh... try again?);
}
void generateQRCode();
}
},
[logInSecondaryDevice, pushModal, generateQRCode],
);
React.useEffect(() => {
void generateQRCode();
}, [generateQRCode]);
const qrCodeURL = React.useMemo(
() => (qrData ? qrCodeLinkURL(qrData.aesKey, qrData.deviceID) : undefined),
[qrData],
);
const qrAuthInput = React.useMemo(
() => ({
secondaryDeviceID: qrData?.deviceID,
aesKey: qrData?.aesKey,
performSecondaryDeviceRegistration: performRegistration,
composeMessage: composeTunnelbrokerMessage,
processMessage: parseTunnelbrokerMessage,
}),
[qrData, performRegistration],
);
useQRAuth(qrAuthInput);
return (
Log in to Comm
Open the Comm app on your phone and scan the QR code below
How to find the scanner:
Go to Profile
Select Linked devices
Click Add on the top right
);
}
export default QRCodeLogin;
diff --git a/web/account/siwe-login-form.react.js b/web/account/siwe-login-form.react.js
index 808acc69f..8fdb4a886 100644
--- a/web/account/siwe-login-form.react.js
+++ b/web/account/siwe-login-form.react.js
@@ -1,328 +1,328 @@
// @flow
import '@rainbow-me/rainbowkit/styles.css';
import classNames from 'classnames';
import invariant from 'invariant';
import * as React from 'react';
import { useAccount, useWalletClient } from 'wagmi';
import { setDataLoadedActionType } from 'lib/actions/client-db-store-actions.js';
import {
getSIWENonce,
getSIWENonceActionTypes,
legacySiweAuth,
legacySiweAuthActionTypes,
} from 'lib/actions/siwe-actions.js';
import {
identityGenerateNonceActionTypes,
useIdentityGenerateNonce,
} from 'lib/actions/user-actions.js';
import ConnectedWalletInfo from 'lib/components/connected-wallet-info.react.js';
import SWMansionIcon from 'lib/components/swmansion-icon.react.js';
import stores from 'lib/facts/stores.js';
import { useWalletLogIn } from 'lib/hooks/login-hooks.js';
import { useLegacyAshoatKeyserverCall } from 'lib/keyserver-conn/legacy-keyserver-call.js';
import { legacyLogInExtraInfoSelector } from 'lib/selectors/account-selectors.js';
import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js';
import type {
LegacyLogInStartingPayload,
LegacyLogInExtraInfo,
} from 'lib/types/account-types.js';
import { SIWEMessageTypes } from 'lib/types/siwe-types.js';
import { getMessageForException } from 'lib/utils/errors.js';
import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js';
import { useDispatch } from 'lib/utils/redux-utils.js';
import { usingCommServicesAccessToken } from 'lib/utils/services-utils.js';
import {
createSIWEMessage,
getSIWEStatementForPublicKey,
siweMessageSigningExplanationStatements,
} from 'lib/utils/siwe-utils.js';
import HeaderSeparator from './header-separator.react.js';
import css from './siwe.css';
import Button from '../components/button.react.js';
import OrBreak from '../components/or-break.react.js';
import { olmAPI } from '../crypto/olm-api.js';
import LoadingIndicator from '../loading-indicator.react.js';
import { useSelector } from '../redux/redux-utils.js';
import { getVersionUnsupportedError } from '../utils/version-utils.js';
type SIWELogInError = 'account_does_not_exist' | 'client_version_unsupported';
type SIWELoginFormProps = {
+cancelSIWEAuthFlow: () => void,
};
const legacyGetSIWENonceLoadingStatusSelector = createLoadingStatusSelector(
getSIWENonceActionTypes,
);
const identityGenerateNonceLoadingStatusSelector = createLoadingStatusSelector(
identityGenerateNonceActionTypes,
);
const legacySiweAuthLoadingStatusSelector = createLoadingStatusSelector(
legacySiweAuthActionTypes,
);
function SIWELoginForm(props: SIWELoginFormProps): React.Node {
const { address } = useAccount();
const { data: signer } = useWalletClient();
const dispatchActionPromise = useDispatchActionPromise();
const legacyGetSIWENonceCall = useLegacyAshoatKeyserverCall(getSIWENonce);
const legacyGetSIWENonceCallLoadingStatus = useSelector(
legacyGetSIWENonceLoadingStatusSelector,
);
const identityGenerateNonce = useIdentityGenerateNonce();
const identityGenerateNonceLoadingStatus = useSelector(
identityGenerateNonceLoadingStatusSelector,
);
const siweAuthLoadingStatus = useSelector(
legacySiweAuthLoadingStatusSelector,
);
const legacySiweAuthCall = useLegacyAshoatKeyserverCall(legacySiweAuth);
const legacyLogInExtraInfo = useSelector(legacyLogInExtraInfoSelector);
const walletLogIn = useWalletLogIn();
const [siweNonce, setSIWENonce] = React.useState(null);
const siweNonceShouldBeFetched =
!siweNonce &&
legacyGetSIWENonceCallLoadingStatus !== 'loading' &&
identityGenerateNonceLoadingStatus !== 'loading';
React.useEffect(() => {
if (!siweNonceShouldBeFetched) {
return;
}
if (usingCommServicesAccessToken) {
void dispatchActionPromise(
identityGenerateNonceActionTypes,
(async () => {
const response = await identityGenerateNonce();
setSIWENonce(response);
})(),
);
} else {
void dispatchActionPromise(
getSIWENonceActionTypes,
(async () => {
const response = await legacyGetSIWENonceCall();
setSIWENonce(response);
})(),
);
}
}, [
dispatchActionPromise,
identityGenerateNonce,
legacyGetSIWENonceCall,
siweNonceShouldBeFetched,
]);
const callLegacySIWEAuthEndpoint = React.useCallback(
async (
message: string,
signature: string,
extraInfo: LegacyLogInExtraInfo,
) => {
await olmAPI.initializeCryptoAccount();
const userPublicKey = await olmAPI.getUserPublicKey();
try {
return await legacySiweAuthCall({
message,
signature,
signedIdentityKeysBlob: {
payload: userPublicKey.blobPayload,
signature: userPublicKey.signature,
},
doNotRegister: true,
...extraInfo,
});
} catch (e) {
const messageForException = getMessageForException(e);
if (messageForException === 'account_does_not_exist') {
setError('account_does_not_exist');
} else if (messageForException === 'client_version_unsupported') {
setError('client_version_unsupported');
}
throw e;
}
},
[legacySiweAuthCall],
);
const attemptLegacySIWEAuth = React.useCallback(
(message: string, signature: string) => {
return dispatchActionPromise(
legacySiweAuthActionTypes,
callLegacySIWEAuthEndpoint(message, signature, legacyLogInExtraInfo),
undefined,
({
calendarQuery: legacyLogInExtraInfo.calendarQuery,
}: LegacyLogInStartingPayload),
);
},
[callLegacySIWEAuthEndpoint, dispatchActionPromise, legacyLogInExtraInfo],
);
const attemptWalletLogIn = React.useCallback(
async (
walletAddress: string,
siweMessage: string,
siweSignature: string,
) => {
try {
return await walletLogIn(walletAddress, siweMessage, siweSignature);
} catch (e) {
const messageForException = getMessageForException(e);
if (messageForException === 'user_not_found') {
setError('account_does_not_exist');
} else if (
messageForException === 'client_version_unsupported' ||
- messageForException === 'Unsupported version'
+ messageForException === 'unsupported_version'
) {
setError('client_version_unsupported');
}
throw e;
}
},
[walletLogIn],
);
const dispatch = useDispatch();
const onSignInButtonClick = React.useCallback(async () => {
invariant(signer, 'signer must be present during SIWE attempt');
invariant(siweNonce, 'nonce must be present during SIWE attempt');
await olmAPI.initializeCryptoAccount();
const {
primaryIdentityPublicKeys: { ed25519 },
} = await olmAPI.getUserPublicKey();
const statement = getSIWEStatementForPublicKey(
ed25519,
SIWEMessageTypes.MSG_AUTH,
);
const message = createSIWEMessage(address, statement, siweNonce);
const signature = await signer.signMessage({ message });
if (usingCommServicesAccessToken) {
await attemptWalletLogIn(address, message, signature);
} else {
await attemptLegacySIWEAuth(message, signature);
dispatch({
type: setDataLoadedActionType,
payload: {
dataLoaded: true,
},
});
}
}, [
address,
attemptLegacySIWEAuth,
attemptWalletLogIn,
signer,
siweNonce,
dispatch,
]);
const { cancelSIWEAuthFlow } = props;
const backButtonColor = React.useMemo(
() => ({ backgroundColor: '#211E2D' }),
[],
);
const signInButtonColor = React.useMemo(
() => ({ backgroundColor: '#6A20E3' }),
[],
);
const [error, setError] = React.useState();
const mainMiddleAreaClassName = classNames({
[css.mainMiddleArea]: true,
[css.hidden]: !!error,
});
const errorOverlayClassNames = classNames({
[css.errorOverlay]: true,
[css.hidden]: !error,
});
if (siweAuthLoadingStatus === 'loading' || !siweNonce) {
return (
);
}
let errorText;
if (error === 'account_does_not_exist') {
errorText = (
<>
No Comm account found for that Ethereum wallet!
We require that users register on their mobile devices. Comm relies on
a primary device capable of scanning QR codes in order to authorize
secondary devices.
You can install our iOS app
here
, or our Android app
here
.
>
);
} else if (error === 'client_version_unsupported') {
errorText = {getVersionUnsupportedError()}
;
}
return (
Sign in with Ethereum
{errorText}
);
}
export default SIWELoginForm;
diff --git a/web/account/traditional-login-form.react.js b/web/account/traditional-login-form.react.js
index 2bb738454..5459874a9 100644
--- a/web/account/traditional-login-form.react.js
+++ b/web/account/traditional-login-form.react.js
@@ -1,267 +1,267 @@
// @flow
import invariant from 'invariant';
import * as React from 'react';
import {
useLegacyLogIn,
legacyLogInActionTypes,
} from 'lib/actions/user-actions.js';
import { useModalContext } from 'lib/components/modal-provider.react.js';
import { usePasswordLogIn } from 'lib/hooks/login-hooks.js';
import { legacyLogInExtraInfoSelector } from 'lib/selectors/account-selectors.js';
import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js';
import {
oldValidUsernameRegex,
validEmailRegex,
} from 'lib/shared/account-utils.js';
import type {
LegacyLogInExtraInfo,
LegacyLogInStartingPayload,
} from 'lib/types/account-types.js';
import { logInActionSources } from 'lib/types/account-types.js';
import { getMessageForException } from 'lib/utils/errors.js';
import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js';
import { usingCommServicesAccessToken } from 'lib/utils/services-utils.js';
import HeaderSeparator from './header-separator.react.js';
import css from './log-in-form.css';
import PasswordInput from './password-input.react.js';
import Button from '../components/button.react.js';
import { olmAPI } from '../crypto/olm-api.js';
import LoadingIndicator from '../loading-indicator.react.js';
import Input from '../modals/input.react.js';
import { useSelector } from '../redux/redux-utils.js';
import { getShortVersionUnsupportedError } from '../utils/version-utils.js';
const loadingStatusSelector = createLoadingStatusSelector(
legacyLogInActionTypes,
);
function TraditionalLoginForm(): React.Node {
const legacyAuthInProgress = useSelector(loadingStatusSelector) === 'loading';
const [identityAuthInProgress, setIdentityAuthInProgress] =
React.useState(false);
const inputDisabled = legacyAuthInProgress || identityAuthInProgress;
const legacyLoginExtraInfo = useSelector(legacyLogInExtraInfoSelector);
const callLegacyLogIn = useLegacyLogIn();
const dispatchActionPromise = useDispatchActionPromise();
const modalContext = useModalContext();
const usernameInputRef = React.useRef();
React.useEffect(() => {
usernameInputRef.current?.focus();
}, []);
const [username, setUsername] = React.useState('');
const onUsernameChange = React.useCallback(
(e: SyntheticEvent) => {
invariant(e.target instanceof HTMLInputElement, 'target not input');
setUsername(e.target.value);
},
[],
);
const onUsernameBlur = React.useCallback(() => {
setUsername(untrimmedUsername => untrimmedUsername.trim());
}, []);
const [password, setPassword] = React.useState('');
const onPasswordChange = React.useCallback(
(e: SyntheticEvent) => {
invariant(e.target instanceof HTMLInputElement, 'target not input');
setPassword(e.target.value);
},
[],
);
const [errorMessage, setErrorMessage] = React.useState('');
const legacyLogInAction = React.useCallback(
async (extraInfo: LegacyLogInExtraInfo) => {
await olmAPI.initializeCryptoAccount();
const userPublicKey = await olmAPI.getUserPublicKey();
try {
const result = await callLegacyLogIn({
...extraInfo,
username,
password,
authActionSource: logInActionSources.logInFromWebForm,
signedIdentityKeysBlob: {
payload: userPublicKey.blobPayload,
signature: userPublicKey.signature,
},
});
modalContext.popModal();
return result;
} catch (e) {
setUsername('');
setPassword('');
const messageForException = getMessageForException(e);
if (messageForException === 'invalid_credentials') {
setErrorMessage('incorrect username or password');
} else if (messageForException === 'client_version_unsupported') {
setErrorMessage(getShortVersionUnsupportedError());
} else {
setErrorMessage('unknown error');
}
usernameInputRef.current?.focus();
throw e;
}
},
[callLegacyLogIn, modalContext, password, username],
);
const callIdentityPasswordLogIn = usePasswordLogIn();
const identityPasswordLogInAction = React.useCallback(async () => {
if (identityAuthInProgress) {
return;
}
setIdentityAuthInProgress(true);
try {
await callIdentityPasswordLogIn(username, password);
modalContext.popModal();
} catch (e) {
setUsername('');
setPassword('');
const messageForException = getMessageForException(e);
if (
messageForException === 'user_not_found' ||
messageForException === 'login failed'
) {
setErrorMessage('incorrect username or password');
} else if (
messageForException === 'client_version_unsupported' ||
- messageForException === 'Unsupported version'
+ messageForException === 'unsupported_version'
) {
setErrorMessage(getShortVersionUnsupportedError());
} else {
setErrorMessage('unknown error');
}
usernameInputRef.current?.focus();
throw e;
} finally {
setIdentityAuthInProgress(false);
}
}, [
identityAuthInProgress,
callIdentityPasswordLogIn,
modalContext,
password,
username,
]);
const onSubmit = React.useCallback(
(event: SyntheticEvent) => {
event.preventDefault();
if (username.search(validEmailRegex) > -1) {
setUsername('');
setErrorMessage('usernames only, not emails');
usernameInputRef.current?.focus();
return;
} else if (username.search(oldValidUsernameRegex) === -1) {
setUsername('');
setErrorMessage('alphanumeric usernames only');
usernameInputRef.current?.focus();
return;
} else if (password === '') {
setErrorMessage('password is empty');
usernameInputRef.current?.focus();
return;
}
if (usingCommServicesAccessToken) {
void identityPasswordLogInAction();
} else {
void dispatchActionPromise(
legacyLogInActionTypes,
legacyLogInAction(legacyLoginExtraInfo),
undefined,
({
calendarQuery: legacyLoginExtraInfo.calendarQuery,
}: LegacyLogInStartingPayload),
);
}
},
[
dispatchActionPromise,
identityPasswordLogInAction,
legacyLogInAction,
legacyLoginExtraInfo,
username,
password,
],
);
const loadingIndicatorClassName = inputDisabled
? css.loadingIndicator
: css.hiddenLoadingIndicator;
const buttonTextClassName = inputDisabled
? css.invisibleButtonText
: undefined;
const loginButtonContent = React.useMemo(
() => (
<>
Sign in
>
),
[loadingIndicatorClassName, buttonTextClassName],
);
const signInButtonColor = React.useMemo(
() => ({ backgroundColor: '#6A20E3' }),
[],
);
return (
);
}
export default TraditionalLoginForm;